package com.zl.autocode.common.utils.generator;

import java.io.BufferedReader;
import java.io.BufferedWriter;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.InputStreamReader;
import java.io.OutputStreamWriter;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

import org.apache.commons.lang3.StringUtils;
import org.dom4j.Attribute;
import org.dom4j.Document;
import org.dom4j.Element;
import org.dom4j.Namespace;
import org.dom4j.QName;
import org.dom4j.io.SAXReader;
import org.springframework.stereotype.Component;

import com.zl.autocode.common.constant.AutocodeConfigC;
import com.zl.autocode.common.utils.generator.model.ConvertColumn;
import com.zl.autocode.common.utils.generator.model.ConvertConfig;
import com.zl.autocode.common.utils.generator.model.ConvertSequence;
import com.zl.autocode.common.utils.generator.model.ConvertTable;
import com.zl.autocode.common.utils.generator.util.TableTitleUtil;

import cn.hutool.core.io.FileUtil;
import cn.hutool.core.util.StrUtil;
import lombok.extern.slf4j.Slf4j;


@Slf4j
@Component("pdm"+AutocodeConfigC.GENERATOR_READER_KEY)
public class ReaderPdm implements IReaderData<ConvertConfig> {
    
    private static String DEFAULT_TYPE = "默认分类";
    private static String DEFAULT_ENCODE = "UTF-8";
    
    static{

    }
    
    @Override
    public void parseData(ConvertConfig configParam) throws Exception {
        // TODO Auto-generated method stub
        log.info("pdm加载方式");
        long start = System.currentTimeMillis();
        
        File file = new File(configParam.getPdmPath());
        
        List<ConvertTable>  tableLists = new ArrayList<>();
        List<ConvertSequence>  sequenceLists =  new ArrayList<>();
        
        if(file.exists()){
            if(file.isDirectory()){
                File[] allFiles = file.listFiles();
                for(File f: allFiles){
                    if(f.isFile()){
                        readerEntry(configParam,tableLists,sequenceLists,f.getPath(),true);
                    }
                }
            }else{
                readerEntry(configParam,tableLists,sequenceLists,configParam.getPdmPath(),true);
            }
        }
        
        log.info("Data loading use time:" + (System.currentTimeMillis() - start) / 1000F + "s");
    }
    @Override
    public Map<String,Object> getData(ConvertConfig configParam) throws Exception {
        // TODO Auto-generated method stub
        long start = System.currentTimeMillis();
        
        Map<String,Object> result = new HashMap<>();
        
        List<ConvertTable>  tableLists = new ArrayList<>();
        List<ConvertSequence>  sequenceLists =  new ArrayList<>();
        
        File file = new File(configParam.getPdmPath());
        if(file.exists()){
            if(file.isDirectory()){
                File[] allFiles = file.listFiles();
                for(File f: allFiles){
                    if(f.isFile()){
                        readerEntry(configParam,tableLists,sequenceLists,f.getPath(),false);
                    }
                }
            }else{
                readerEntry(configParam,tableLists,sequenceLists,configParam.getPdmPath(),false);
            }
        }
        result.put("tables", configParam.getTbls());
        
        log.info("Data loading use time:" + (System.currentTimeMillis() - start) / 1000F + "s");
        
        return result;
    }
    
    private static void readerEntry(ConvertConfig configParam, List<ConvertTable>  tableLists,List<ConvertSequence>  sequenceLists
            ,String path,boolean allFlag) throws Exception {
        Document xmldoc = parsePdm(path, configParam.getCharacterEncoding());
        
        String loadSource = StringUtils.isNotBlank(configParam.getLoadSourceType())?configParam.getLoadSourceType():"1";
        //加载表信息
        if("0".equals(loadSource)||"1".equals(loadSource)){
            parseTable(tableLists,xmldoc,configParam,allFlag);
            configParam.setTbls(tableLists);
        }
        
        
        //加载自增序列
        if("0".equals(loadSource)||"2".equals(loadSource)){
            parseSequence(sequenceLists,xmldoc,configParam,allFlag);
            configParam.setSeqs(sequenceLists);
        }
    }
    
    /**
     * 加载需要解析的PDM文件,并创建备份用于解析
     * 
     * @param filename： 需要解析的PDM文件
     * @param charset：编码格式
     * @return 
     * @return
     * @throws Exception
     */
    private static Document parsePdm(String filename, String charset) throws Exception {
        charset = StrUtil.isNotBlank(charset)?charset:DEFAULT_ENCODE;
        
        //进行文件复制
        File copyfile = FileUtil.createTempFile("zlfile", null,null,true);
        BufferedWriter bw = new BufferedWriter(new OutputStreamWriter(new FileOutputStream(copyfile), charset));
        BufferedReader br = new BufferedReader(new InputStreamReader(new FileInputStream(filename), charset));
        String line = null;
        while ((line = br.readLine()) != null) {
            bw.write(line.replaceAll("[\\x00-\\x08\\x0b-\\x0c\\x0e-\\x1f]", ""));
            bw.newLine();
        }
        bw.flush();
        bw.close();
        br.close();
        
        SAXReader reader = new SAXReader();
        Document document = reader.read(new InputStreamReader(new FileInputStream(copyfile), charset));
        //删掉临时文件
        copyfile.deleteOnExit();
        return document;
    }

    /**
     * 获取所有表数据
     */
    @SuppressWarnings("unchecked")
    public static void parseTable(List<ConvertTable> tableLists,Document document, ConvertConfig configParam,boolean allFlag) throws Exception {
        Element rootElement = document.getRootElement();
        Namespace oNamespace = new Namespace("o", "object");
        Namespace cNamespace = new Namespace("c", "collection");
        Namespace aNamespace = new Namespace("a", "attribute");
        Element rootObject = rootElement.element(new QName("RootObject", oNamespace));
        Element children = rootObject.element(new QName("Children", cNamespace));
        Element model = children.element(new QName("Model", oNamespace));
        
        Map<String,List<Element>> packMapEles = new LinkedHashMap<String,List<Element>>();
        // 解析所有表
        getAllpackagesForTable(packMapEles,model,oNamespace,cNamespace,aNamespace);
        
        //获得表详细内容
        for (Map.Entry<String, List<Element>> packMapEle : packMapEles.entrySet()) {
            String packageName = packMapEle.getKey();//包名用于分类
            List<Element> tableEles = packMapEle.getValue();//表
            
            for (Element tableElement : tableEles) {
                Element name = tableElement.element(new QName("Name", aNamespace));
                Element code = tableElement.element(new QName("Code", aNamespace));
                Element comment = tableElement.element(new QName("Comment", aNamespace));
                
                
                //如果有选中的表,则只生成选中的表,否则全部加载
                List<String> selectedTables = configParam.getLoadTables();
                String queryType = StringUtils.isNotBlank(configParam.getQueryType())?configParam.getQueryType():"0";
                if(selectedTables != null && selectedTables.size()>0 
                        &&(("0".equals(queryType) && !selectedTables.stream().anyMatch(x-> x.equalsIgnoreCase(getTextFromEle(code))))
                           || !selectedTables.stream().anyMatch(x-> getTextFromEle(code).toLowerCase().contains(x.toLowerCase())))){
                    continue;
                }
                
                log.info(packageName+"-Table-{}",getTextFromEle(code).toLowerCase());
                
                try{
                    ConvertTable ctbl = new ConvertTable();
                    ctbl.setTableName(getTextFromEle(code));//原始code
                    ctbl.setTableCode(getTextFromEle(code).toLowerCase());
                    ctbl.setTableComment(getTextFromEle(comment));
                    ctbl.setTableTag(packageName);
                    //特殊处理NAME
                    String tableTitle = getTextFromEle(name);
                    if (StrUtil.isNotBlank(tableTitle)){
                        ctbl.setTableTitle(TableTitleUtil.praseTitleByPattern(tableTitle,ctbl.getTableCode()));
                        if(ctbl.getTableCode().equalsIgnoreCase(ctbl.getTableTitle())) {
                        	 ctbl.setTableTitle(TableTitleUtil.praseTitleByPattern(ctbl.getTableComment(),ctbl.getTableCode()));
                        }
                    }
                    
                    if(allFlag){
                     // 解析主键
                        Element primaryKeyEle = tableElement.element(new QName("PrimaryKey", cNamespace));
                        List<String> pkIds = new ArrayList<>();
                        if (primaryKeyEle != null) {
                            List<Element> pks = primaryKeyEle.elements(new QName("Key", oNamespace));
                            for (Element pk1 : pks) {
                                pkIds.add(pk1.attribute("Ref").getValue());
                            }
                        }
                        Element keysEle = tableElement.element(new QName("Keys", cNamespace));
                        List<String> pkColumnIds = new ArrayList<>();
                        if (keysEle != null) {
                            List<Element> keyEleList = keysEle.elements(new QName("Key", oNamespace));
                            for (Element keyEle : keyEleList) {
                                Attribute id = keyEle.attribute("Id");
                                if (pkIds.contains(id.getValue())) {
                                    List<Element> list = keyEle.element(new QName("Key.Columns", cNamespace))
                                            .elements(new QName("Column", oNamespace));
                                    if(list!=null){
                                        for (Element element : list) {
                                            pkColumnIds.add(element.attribute("Ref").getValue());
                                        }
                                    }else{
                                        log.error("【{}-{}】Table Key error>>{}",packageName,getTextFromEle(code),"主键字段缺失");
                                    }
                                }
                            }
                        }
                        
                        // 解析column
                        List<Element> columns = tableElement.element(new QName("Columns", cNamespace))
                                .elements(new QName("Column", oNamespace));
                        
                        
                        List<ConvertColumn> allcols = new ArrayList<ConvertColumn>();//主键字段
                        
                        for (Element columnEle : columns) {
                            String columnId = columnEle.attribute("Id").getValue();
                            Element cname = columnEle.element(new QName("Name", aNamespace));
                            Element ccode = columnEle.element(new QName("Code", aNamespace));
                            Element cDataType = columnEle.element(new QName("DataType", aNamespace));
                            Element cLength = columnEle.element(new QName("Length", aNamespace));
                            Element cPrecision = columnEle.element(new QName("Precision", aNamespace));
                            Element cComment = columnEle.element(new QName("Comment", aNamespace));
                            Element nullable = columnEle.element(new QName("Column.Mandatory", aNamespace));
                            Element defaultValue = columnEle.element(new QName("DefaultValue", aNamespace));
                            
                            //是否主键
                            String pkFlag = pkColumnIds.contains(columnId)?"1":"0";//0否,1是
                            //是否为空
                            String nullflag = getBooleanEle(nullable)?"0":"1";//0否,1是
                            //长度
                            Integer length = getIntegerEle(cLength);
                            //小数长度
                            Integer precision = getIntegerEle(cPrecision);
                           
                            ConvertColumn col = new ConvertColumn();
                            col.setColumnName(getTextFromEle(ccode));//原始code
                            col.setColumnCode(getTextFromEle(ccode).toLowerCase());
                            col.setColumnDatatype(getTextFromEle(cDataType));
                            col.setColumnLength(length == null?null:length.toString());
                            col.setColumnPrecision(precision == null?null:precision.toString());
                            col.setColumnPrimarykey(pkFlag);
                            col.setColumnMandatory(nullflag);
                            col.setColumnComment(getTextFromEle(cComment));
                            col.setColumnDefault(getTextFromEle(defaultValue));
                            //特殊处理NAME
                            String columnTitle = getTextFromEle(cname);
                            if (StrUtil.isNotBlank(columnTitle)){
                                col.setColumnTitle(TableTitleUtil.praseTitleByPattern(columnTitle,col.getColumnCode()));
                                if(col.getColumnCode().equalsIgnoreCase(col.getColumnTitle())) {
                                	col.setColumnTitle(TableTitleUtil.praseTitleByPattern(col.getColumnComment(),col.getColumnCode()));
                                }
                            }
                            
                            allcols.add(col);
                        }
                        
                        ctbl.setAllcols(allcols);//当前表字段
                    }
                    
                    tableLists.add(ctbl);//当前表
                }catch(Exception e){
                    log.error("【{}-{}】Table loading error>>{}",packageName,getTextFromEle(code),e);
                }
            }
        }
    }
    @SuppressWarnings("unchecked")
    private static void getAllpackagesForTable(Map<String,List<Element>> packMapEles,Element model, Namespace oNamespace,
            Namespace cNamespace, Namespace aNamespace) throws Exception {
        // 解析package中的表
        Element packagesEle = model.element(new QName("Packages", cNamespace));
        if (packagesEle != null) {
            List<Element> packageEles = packagesEle.elements(new QName("Package", oNamespace));
            for (Element packageEle : packageEles) {
                //获得包名，用于分类
                Element cpack = packageEle.element(new QName("Name", aNamespace));
                String packageName = getTextFromEle(cpack);
                //获得表
                Element tablesEle = packageEle.element(new QName("Tables", cNamespace));
                if (tablesEle != null) {
                    packMapEles.put(packageName, tablesEle.elements(new QName("Table", oNamespace)));
                }
                //再判断是否有包
                getAllpackagesForTable(packMapEles,packageEle, oNamespace,cNamespace, aNamespace);
            }
        }
        // 直接解析table,未放在包下面的
        List<Element> tablesEles = model.elements(new QName("Tables", cNamespace));
        if (tablesEles != null && tablesEles.size()>0 && !"Package".equalsIgnoreCase(tablesEles.get(0).getParent().getQName().getName())) {
            packMapEles.put(DEFAULT_TYPE, tablesEles.get(0).elements(new QName("Table", oNamespace)));
        }
    }
    
    /**
     * 读取pdm文件中的Sequence
     */
    public static void parseSequence(List<ConvertSequence>  sequenceLists,Document document, ConvertConfig configParam,boolean allFlag) throws Exception {
        
        Element rootElement = document.getRootElement();
        Namespace oNamespace = new Namespace("o", "object");
        Namespace cNamespace = new Namespace("c", "collection");
        Namespace aNamespace = new Namespace("a", "attribute");
        Element rootObject = rootElement.element(new QName("RootObject", oNamespace));
        Element children = rootObject.element(new QName("Children", cNamespace));
        Element model = children.element(new QName("Model", oNamespace));
        
        Map<String,List<Element>> packMapEles = new LinkedHashMap<String,List<Element>>();
        // 解析所有SEQ
        getAllpackagesForSequence(packMapEles,model,oNamespace,cNamespace,aNamespace);
        
        
        for (Map.Entry<String, List<Element>> packMapEle : packMapEles.entrySet()) {
            String packageName = packMapEle.getKey();//包名用于分类
            List<Element> seqsEles = packMapEle.getValue();//表
            
            for(Element seqsElement : seqsEles){
                Element name = seqsElement.element(new QName("Name", aNamespace));
                Element code = seqsElement.element(new QName("Code", aNamespace));
                Element comment = seqsElement.element(new QName("Comment", aNamespace));
                Element physicalOptions = seqsElement.element(new QName("PhysicalOptions", aNamespace));
                
                //如果有选中的序号,则只生成选中的,否则全部加载
                List<String> selectedSeqs = configParam.getLoadSeqs();
                String queryType = StringUtils.isNotBlank(configParam.getQueryType())?configParam.getQueryType():"0";
                if(selectedSeqs != null && selectedSeqs.size()>0 
                        &&(("0".equals(queryType) && !selectedSeqs.stream().anyMatch(x-> x.equalsIgnoreCase(getTextFromEle(code))))
                           || !selectedSeqs.stream().anyMatch(x-> getTextFromEle(code).toLowerCase().startsWith(x.toLowerCase())))){
                    continue;
                }
                
                log.info("Sequence-{}",getTextFromEle(code).toLowerCase());
                
                try{
                    ConvertSequence seq = new ConvertSequence();
                    seq.setSequenceName(getTextFromEle(code));
                    seq.setSequenceCode(getTextFromEle(code).toLowerCase());
                    seq.setSequenceComment(getTextFromEle(comment));
                    seq.setSequenceTag(packageName);
                    
                    String textArr[] = physicalOptions.getStringValue().split("\n");
                    String maxtext[] = textArr[2].trim().split(" ");
                    if (maxtext.length > 1) {
                        seq.setSequenceMax(maxtext[1].trim());// 序列最大值属性
                    }
                    String mintext[] = textArr[3].trim().split(" ");
                    if (mintext.length > 1) {
                        seq.setSequenceMax(mintext[1].trim());// 序列最小值属性
                    }
                    //特殊处理NAME
                    String sequenceTitle = getTextFromEle(name);
                    if (StrUtil.isNotBlank(sequenceTitle)){
                        seq.setSequenceTitle(TableTitleUtil.praseTitleByPattern(sequenceTitle,seq.getSequenceCode()));
                        if(seq.getSequenceCode().equalsIgnoreCase(seq.getSequenceTitle())) {
                        	seq.setSequenceTitle(TableTitleUtil.praseTitleByPattern(seq.getSequenceComment(),seq.getSequenceCode()));
                        }
                    }
                    
                    sequenceLists.add(seq);
                }catch(Exception e){
                    log.error("【{}-{}】Sequence loading error>>{}",packageName,getTextFromEle(code),e);
                }
            }
        }
    }
    @SuppressWarnings("unchecked")
    private static void getAllpackagesForSequence(Map<String,List<Element>> packMapEles,Element model, Namespace oNamespace,
            Namespace cNamespace, Namespace aNamespace) throws Exception {
        // 解析package中的SEQ
        Element packagesEle = model.element(new QName("Packages", cNamespace));
        if (packagesEle != null) {
            List<Element> packageEles = packagesEle.elements(new QName("Package", oNamespace));
            for (Element packageEle : packageEles) {
                //获得包名，用于分类
                Element cpack = packageEle.element(new QName("Name", aNamespace));
                String packageName = getTextFromEle(cpack);
                
                //获得表
                Element seqsEle = packageEle.element(new QName("Sequences", cNamespace));
                if (seqsEle != null) {
                    packMapEles.put(packageName, seqsEle.elements(new QName("Sequence", oNamespace)));
                }
            }
        }
        
        // 直接解析SEQ,未放在包下面的
        List<Element> seqsEles = model.elements(new QName("Sequences", cNamespace));
        if (seqsEles != null && seqsEles.size()>0 && !"Package".equalsIgnoreCase(seqsEles.get(0).getParent().getQName().getName())) {
            packMapEles.put(DEFAULT_TYPE, seqsEles.get(0).elements(new QName("Sequence", oNamespace)));
        }
    }
    
    /**取得字符串*/
    private static String getTextFromEle(Element element) {
        if (element == null) {
            return "";
        }
        return element.getTextTrim();
    }
    /**获得Int数值*/
    private static Integer getIntegerEle(Element element) {
        if (element == null) {
            return null;
        }
        return Integer.valueOf(Integer.parseInt(element.getTextTrim()));
    }    
    /**取得true/false*/
    private static boolean getBooleanEle(Element element) {
        if (element == null) {
            return false;
        }
        return element.getTextTrim().equals("1");
    }
    
}
